/** @file    WeatherWorker.cpp
 *  @author  Alessandro Polo
 *  @version $Id: WeatherWorker.cpp 2859 2010-08-07 00:45:36Z alex $
 *  @brief
 * File containing methods for the WeatherWorker class.
 * The header for this class can be found in WeatherWorker.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "WeatherWorker.h"

 #include <algorithm>


using namespace std;
using namespace wosh;

bool WeatherSourceSortPredicate(const WeatherSource* d1, const WeatherSource* d2) {
	if ( d1 == NULL ) return true;
	if ( d2 == NULL ) return false;
	if ( d1->getPriority() < d2->getPriority() ) return true;
	if ( d1->getReliability() > d2->getReliability() ) return false;
	return d1->getLastUpdated() < d2->getLastUpdated();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

WeatherWorker::WeatherWorker( BundleGeneric& bundle )
	: BundleGenericWorker(bundle) {
	this->listener = NULL;
	this->historyMax = 0;
 }


WeatherWorker::~WeatherWorker() {
	if ( isThreadRunning() ) {
		quitThread(10000);
	 }
	Log(LOG_VERBOSE, ":~WeatherWorker() : Freeing Sources.." );
	this->sources.clear();

	Log(LOG_VERBOSE, ":~WeatherWorker() : Freeing History.." );
	this->history.clear();

	Log(LOG_VERBOSE, ":~WeatherWorker() : Freeing Forecast.." );
	this->forecast.clear();
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD

void WeatherWorker::runThread() {
	Log(LOG_DEBUG, "runThread() : Entering Loop.." );
	WRESULT ret; bool newWeatherAvailable;
	double currentWeatherSimilarity, currentWeatherDataFusion;
	WeatherInfo newCurrentWeather, newCurrentWeatherMerged;
	std::vector<WeatherInfo*> forecasts, forecastsMerged;
	tWeatherSourceList::ConstIterator it;
	while( this->running ) {
		setThreadAlive();
		// retrieve weather info from sources
		newWeatherAvailable = false;
		newCurrentWeather.clear(); newCurrentWeatherMerged.clear();
		this->sources.transactionBeginRead();
		for ( it=this->sources.begin(); it!=this->sources.end(); ++it ) {
			if ( *it == NULL ) continue;
			if ( !(*it)->isEnabled() ) continue;
			if ( (*it)->shouldBeUpdated() ) {
				// update available information about weather.. for example it may download XML file
				setThreadAlive();
				ret = (*it)->updateData( 15000, &newWeatherAvailable);
				if ( WFAILED(ret) ) {
					continue;
				 }
			 }
			if ( !newWeatherAvailable ) {
				continue; // no news
			 }
			setThreadAlive();
			// load (parse) news into temp containers
			ret = (*it)->loadForecast( &newCurrentWeather, &forecasts );
			// (progressive) merge info
			newCurrentWeatherMerged.merge(newCurrentWeather);
			mergeForeCast( forecasts );
		 }
		this->sources.transactionEnd();

		ThreadImpl::sleepWhile(this, 300000, 1000, &this->running);
		if ( this->running ) break;
		setThreadAlive();

		if ( !newCurrentWeatherMerged.isEmpty() ) {
			// data fusion of CURRENT WEATHER
			this->wCurrentInfoMux.lockForWrite();
			currentWeatherSimilarity = this->wCurrentInfo.evalSimilarity(newCurrentWeatherMerged);
			//if ( currentWeatherSimilarity < 1.0 )
			currentWeatherDataFusion = this->wCurrentInfo.merge(newCurrentWeatherMerged);
			this->wCurrentInfoMux.unLock();
			// generate events.. listener's callbacks
			if ( this->listener == NULL ) continue;
			this->listener->updatedWeather(&this->wCurrentInfo, currentWeatherSimilarity, this);
		 }
		else {
			continue;
		 }
// data fusion of FORECAST

		// move OLD weather-info to history, eval history size
		updateHistory();

	 }
	Log(LOG_DEBUG, "runThread() : Exiting Loop.." );
 }

////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT WeatherWorker::addSource( WeatherSource* source ) {
	if ( source == NULL ) return WRET_ERR_PARAM;
	Log(LOG_INFO, "addSource(%s) : Adding Source#%"PRId64"..", source->getName().c_str(), source->getID() );
	this->sources.push_back(source);
	std::sort(this->sources.begin(), this->sources.end(), WeatherSourceSortPredicate);
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

int WeatherWorker::updateHistory() {
	Log(LOG_DEBUG, "updateHistory() : Fixing history.." );
	this->history.transactionBeginWrite();
	if ( this->historyMax > 0 ) {
		int removed = 0;
		while ( this->history.size() > this->historyMax ) {
			this->history.erase(this->history.begin(), true);
			++removed;
		 }
		Log(LOG_DEBUG, "updateHistory() : %d items removed from history..", removed );
	 }
	int count = 0;
	int64 now = Utilities::std_time();
	this->forecast.transactionBeginWrite();
	tInt64WeatherInfoMap::Iterator it;
	const tInt64WeatherInfoMap::Iterator it_end = this->forecast.end();
	for ( it=this->forecast.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		if ( it->second->getPeriodStart() > now ) continue; // start period is in future
		if ( it->second->getPeriodEnd() > now ) continue; // end period is in future
		// forecast period is in the past!
		this->history.push_back(it->second);
		it->second = NULL;
		++count;
	 }
	int moved = this->forecast.clearNullValues();
	Log(LOG_VERBOSE, "updateHistory() : %d forecast-items moved to history..", moved );
	this->forecast.transactionEnd();
	this->history.transactionEnd();
	return count;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void WeatherWorker::mergeForeCast( std::vector<WeatherInfo*>& newForecast ) {
	Log(LOG_VERBOSE, "mergeForeCast() : Merging %d forecast-items..", newForecast.size() );
	// find nearest period, typically its'a day
	this->forecast.transactionBeginWrite();
	std::vector<WeatherInfo*>::iterator it;
	for( it=newForecast.begin(); it!=newForecast.end(); ++it ) {
		if ( *it == NULL ) continue;
		if ( !(*it)->isPeriodValid() ) continue;
		// (todo) not able to merge periods
		WeatherInfo* oldInfo = this->forecast.find( (*it)->getPeriodStart() );
		if ( oldInfo == NULL ) {
			Log(LOG_DEBUG, "mergeForeCast() : Adding forecast.." );
			this->forecast.set((*it)->getPeriodStart(), *it);
			*it = NULL; // forget the pointer since we acquired it
		 }
		else {
			Log(LOG_DEBUG, "mergeForeCast() : Merging forecast.." );
			oldInfo->merge( *(*it) );
			delete *it; *it = NULL; // delete the object
		 }
	 }
	this->forecast.transactionEnd();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
